//
//  BasicBroadcastBus.cpp
//  sad
//
//  Created by Christopher Kemsley on 5/3/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//

#include <list>

#include "SystemCDefinitions.hpp"
#include "BroadcastBusInterface.hpp"

#include "Settings.hpp"
#include "URL.hpp"
#include "Codec.hpp"
#include "Log.hpp"
#include "PubSub.hpp"
#include "Exception.hpp"
#include "Model.hpp"
#include "ModuleFactory.hpp"

#include "SingleFireSignal.hpp"

#include "Statistics.hpp"

BroadcastBusNamespaceBegin







struct	BasicBroadcastBusSettings : public Settings
{
	std::string	masterName ;
	std::string	slaveName ;
	SCInterval	interBurstDelay ;
	SCInterval	delayForAcknowledgement ;
	SCInterval	txDelay ;
	SCInterval	commitTimeout ;
	
	BasicBroadcastBusSettings ( std::string const & name )
	:	Settings(name)
	,	masterName		( )
	,	slaveName		( )
	,	interBurstDelay			( 2 , kSCIntervalMicroSeconds )
	,	delayForAcknowledgement	( 2 , kSCIntervalMicroSeconds )
	,	txDelay					( 2 , kSCIntervalMicroSeconds )
	,	commitTimeout			( 2 , kSCIntervalMicroSeconds )
		{
		add ( "publisherInterfaceName" , masterName ) ;
		add ( "subscriberInterfaceName" , slaveName ) ;
		add ( "interBurstDelay" , interBurstDelay ) ;
		add ( "delayForAcknowledgement" , delayForAcknowledgement ) ;
		add ( "txDelay" , txDelay ) ;
		add ( "commitTimeout" , commitTimeout ) ;
		}
	
	virtual void validate ()
		{
		if ( masterName.size() == 0 )
			Log::error("BasicBroadcastBusSettings") << "Master-interface name's length is 0" ;
		if ( slaveName.size() == 0 )
			Log::error("BasicBroadcastBusSettings") << "Slave-interface name's length is 0" ;
		}
	
	
} ;




char const*const	kStateIdle				=	"Idle" ;
char const*const	kStateWaitingForAck		=	"Waiting for Ack" ;
char const*const	kStateWaitingForCommit	=	"Waiting for Commit" ;
char const*const	kStateTransferring		=	"Transferring" ;
char const*const	kStateInterBurstDelay	=	"Cleaning up" ;





struct BasicBroadcastBus
:	public ConfiguredModel<BasicBroadcastBusSettings>
,	virtual public PublisherInterface
,	virtual public SubscriberInterface
{
	
	/* Hey world: here's our interface! */
	Publication<PublisherInterface>		mMasterPub ;
	Publication<SubscriberInterface>	mSlavePub ;
	
	/* Signals */
	NonDuplicatingNotification	mPublicationWasEnqueued ;
	NonDuplicatingNotification	mReceivedCommit ;
	NonDuplicatingNotification	mCurrentPublicationChanged ;
	NonDuplicatingNotification	mDatumIsReadyForReading ;
	
	/* Queueing */
	std::list<PublicationInfo>	mQueue ;
	PublicationInfo				mCurrentPublication ;
	
	/* Synchronization */
	Flags	mFlags ;
	Length	mRemainingData ;
	Datum	mDatum ;
	
	/* Statistics */
	Stats::Value<char const*>	mState ; // not used as a state machine
	Stats::Value<unsigned>		mPublisherCount ;
	
	
	SC_HAS_PROCESS(BasicBroadcastBus) ;
	
	BasicBroadcastBus ( ModelVariant const & name )
	:	ConfiguredModel<BasicBroadcastBusSettings> ( name )
	,	mMasterPub()
	,	mSlavePub()
	,	mPublicationWasEnqueued(sc_module_name(( name.instanceName+"_publicationWasEnqueued" ).c_str()))
	,	mReceivedCommit(sc_module_name(( name.instanceName+"_receivedCommit" ).c_str()))
	,	mCurrentPublicationChanged(sc_module_name(( name.instanceName+"_currentPublicationChanged" ).c_str()))
	,	mDatumIsReadyForReading(sc_module_name(( name.instanceName+"_dataIsReadyForReading" ).c_str()))
	,	mQueue()
	,	mCurrentPublication()
	,	mFlags()
	,	mRemainingData()
	,	mDatum()
	,	mState(name.instanceName+".state",kStateIdle)
	,	mPublisherCount(name.instanceName+".publisherCount",0)
		{
		PublisherInterface*		thisMaster	=	this ;
		mMasterPub = mConfig.masterName ;
		mMasterPub = thisMaster ;
		
		SubscriberInterface*	thisSlave	=	this ;
		mSlavePub = mConfig.slaveName ;
		mSlavePub = thisSlave ;
		
		SC_THREAD ( _controlLoop ) ;
		}
	
	
	
	void	_controlLoop ()
		{
		while ( 1 )
			{
			mState = kStateIdle ;
			
			/* Wait for a request */
			while ( mQueue.empty() )	mPublicationWasEnqueued . wait () ;
			mFlags = 0 ;
			
			
			/* Setup the environment */
			mCurrentPublication = mQueue.front() ;
			mQueue.pop_front() ;
			mCurrentPublicationChanged . fire () ;
			
			mRemainingData = 0 ;
			
			
			mState = kStateWaitingForAck ;
			wait ( mConfig.delayForAcknowledgement ) ;
			
			mState = kStateWaitingForCommit ;
			mReceivedCommit . wait ( mConfig.commitTimeout ) ;
			
			
			mState = kStateTransferring ;
			/* Wait for it to finish */
			while ( mRemainingData != 0 )
				{
				wait ( mConfig.txDelay ) ;
				mDatumIsReadyForReading . fire () ;
				mRemainingData -- ;
				}
			
			
			mState = kStateInterBurstDelay ;
			/* Wait the inter burst delay */
			wait ( mConfig.interBurstDelay ) ;
			}
		}
	
	
	
	
	
	virtual Flags	enqueueBroadcastAndWait	( PublicationInfo const & info )
		{
		mPublisherCount = mPublisherCount.value() + 1 ;
		
		mQueue . push_back ( info ) ;
		mPublicationWasEnqueued.fire() ;
		
		while (!( mCurrentPublication == info ))
			mCurrentPublicationChanged . wait () ;
		
		wait ( mConfig.delayForAcknowledgement ) ;
		
		mPublisherCount = mPublisherCount.value() - 1 ;
		
		return mFlags ;
		}
	
	virtual void	write ( Datum datum )
		{
		mDatum = datum ;
		mDatumIsReadyForReading . wait ( mConfig.txDelay ) ;
		}
	
	virtual void	commitWriteOperation ()
		{
		mRemainingData = mCurrentPublication.burstLength() ;
		mReceivedCommit.fire() ;
		}
	
	virtual void	waitForPublicationInfo ( PublicationInfo & outInfo )
		{
		mCurrentPublicationChanged . wait () ;
		outInfo = mCurrentPublication ;
		}
	
	virtual void	acknowledgePublication ()
		{
		mFlags |= kFlagReceivedAcceptance ;
		}
	virtual void	raiseObjection ()
		{
		mFlags |= kFlagReceivedObjection ;
		}
	
	virtual void	read ( Datum & datum )
		{
		mDatumIsReadyForReading.wait() ;
		datum = mDatum ;
		}
} ;





TypedModuleMaker<BasicBroadcastBus>	_BasicBroadcastBus ( "Bus.broadcast.timed" ) ;

BroadcastBusNamespaceEnd
